# coding: utf-8

import OpenGL.GL as gl
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

from . import WindowProtocol


class Camera(object):
    def __init__(self, window, pos, look_at, head):
        """ Set up a camera.
        :param pos: the position.
        :param look_at: the position looking at.
        """
        super(Camera, self).__init__()
        self._pos = pos
        self._look_at = look_at
        self._head = head
        self._win = window

    def setup(self, width, height):
        pos_x, pos_y, pos_z = self._pos
        look_at_x, look_at_y, look_at_z = self._look_at
        head_x, head_y, head_z = self._head
        width_height_ratio = width / height

        glPushMatrix()

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        # Set the perspective.
        gluPerspective(45, width_height_ratio, 1, 1000)
        gluLookAt(
                pos_x, pos_y, pos_z,  # Eye position.
                look_at_x, look_at_y, look_at_z,  # where we looking at.
                head_x, head_y, head_z  # where our head points into.
        )

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glPopMatrix()


class GridWindow(WindowProtocol):
    def __init__(self, caption, size=(800, 600)):
        super(GridWindow, self).__init__()
        self._display_queue = []
        self._update_queue = []
        self._camera = Camera(self,
                              (0, 0, -3),
                              (0, 0, 0),
                              (0, 1, 0))
        self._caption = caption
        self._size = size

    @property
    def caption(self):
        return self._caption

    @property
    def width(self):
        return self._size[0]

    @property
    def height(self):
        return self._size[1]

    def time(self, dt, fun=None):
        if fun is None:
            def _wrap(f):
                self._update_queue.append((f, dt))

            return _wrap
        else:
            self._update_queue.append((fun, dt))

    def within_draw_process(self, proc):
        if callable(proc):
            self._display_queue.append(proc)
        else:
            print('Warning: draw process is not callable. ')

    @staticmethod
    def _draw_grid():
        glColor3d(0.5, 0.5, 0.5)
        glBegin(GL_LINES)

        half_col = 5
        half_row = 5

        for i in range(-half_col, half_col + 1):
            glVertex3d(i, 0, -half_row)
            glVertex3d(i, 0, half_row)

        for j in range(-half_row, half_row + 1):
            glVertex3d(-half_col, 0, j)
            glVertex3d(half_col, 0, j)

        glEnd()

    def run(self):
        glutInit()
        glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE)
        glutInitWindowSize(*self._size)
        glutCreateWindow(self._caption)

        self._camera.setup(*self._size)

        def display():
            glPushMatrix()

            glClear(GL_DEPTH_BUFFER_BIT)
            glClear(GL_COLOR_BUFFER_BIT)

            # draw grid.
            self._draw_grid()

            # draw point cloud
            for draw in self._display_queue:
                draw(gl)

            glPopMatrix()

            glFlush()

        glutDisplayFunc(display)

        def update(f):
            def _wrap(dt):
                f(dt)
                display()
                glutTimerFunc(dt, _wrap, 0)

            return _wrap

        for callback, dt in self._update_queue:
            glutTimerFunc(dt, update(callback), 0)

        glClearColor(0.0, 0.0, 0.0, 1.0)

        # glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS)

        glutMainLoop()
